// Copyright Epic Games, Inc. All Rights Reserved.

#include "UI/LyraSimulatedInputWidget.h"
#include "EnhancedInputSubsystems.h"
#include "GenericPlatform/GenericPlatformInputDeviceMapper.h"
#include "LyraLogChannels.h"
#include "InputKeyEventArgs.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraSimulatedInputWidget)

#define LOCTEXT_NAMESPACE "LyraSimulatedInputWidget"

/**
 * 构造函数
 * @param ObjectInitializer 对象初始化器
 */
ULyraSimulatedInputWidget::ULyraSimulatedInputWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer) // 调用父类构造函数
{
	SetConsumePointerInput(true); // 设置消耗指针输入
}

#if WITH_EDITOR // 仅在编辑器环境下编译
/**
 * 获取调色板类别
 * @return 调色板类别文本
 */
const FText ULyraSimulatedInputWidget::GetPaletteCategory()
{
	return LOCTEXT("PalleteCategory", "Input"); // 返回输入类别
}
#endif // WITH_EDITOR 结束

/**
 * 原生构造
 * 查询要模拟的键并监听控制映射重建
 */
void ULyraSimulatedInputWidget::NativeConstruct()
{
	// 查找初始键，然后监听任何控制映射的变化
	QueryKeyToSimulate(); // 查询要模拟的键

	if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem()) // 如果获取到增强输入子系统
	{
		System->ControlMappingsRebuiltDelegate.AddUniqueDynamic(this, &ULyraSimulatedInputWidget::OnControlMappingsRebuilt); // 添加控制映射重建委托
	}
	
	Super::NativeConstruct(); // 调用父类方法
}

/**
 * 原生析构
 * 移除控制映射重建委托
 */
void ULyraSimulatedInputWidget::NativeDestruct()
{
	if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem()) // 如果获取到增强输入子系统
	{
		System->ControlMappingsRebuiltDelegate.RemoveAll(this); // 移除所有控制映射重建委托
	}

	Super::NativeDestruct(); // 调用父类方法
}

/**
 * 触摸结束时的原生处理
 * 刷新模拟输入
 * @param InGeometry 几何信息
 * @param InGestureEvent 手势事件
 * @return 回复
 */
FReply ULyraSimulatedInputWidget::NativeOnTouchEnded(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
{
	FlushSimulatedInput(); // 刷新模拟输入
	
	return Super::NativeOnTouchEnded(InGeometry, InGestureEvent); // 调用父类方法
}

/**
 * 获取增强输入子系统
 * @return 增强输入本地玩家子系统
 */
UEnhancedInputLocalPlayerSubsystem* ULyraSimulatedInputWidget::GetEnhancedInputSubsystem() const
{
	if (APlayerController* PC = GetOwningPlayer()) // 如果获取到所属玩家控制器
	{
		if (ULocalPlayer* LP = GetOwningLocalPlayer()) // 如果获取到所属本地玩家
		{
			return LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(); // 返回增强输入本地玩家子系统
		}
	}
	return nullptr; // 返回空
}

/**
 * 获取玩家输入
 * @return 增强玩家输入
 */
UEnhancedPlayerInput* ULyraSimulatedInputWidget::GetPlayerInput() const
{
	if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem()) // 如果获取到增强输入子系统
	{
		return System->GetPlayerInput(); // 返回玩家输入
	}
	return nullptr; // 返回空
}

/**
 * 输入键值
 * 将值注入到增强输入子系统或直接模拟按键输入
 * @param Value 输入值
 */
void ULyraSimulatedInputWidget::InputKeyValue(const FVector& Value)
{
	const APlayerController* PC = GetOwningPlayer(); // 获取所属玩家控制器
	const FPlatformUserId UserId = PC ? PC->GetPlatformUserId() : PLATFORMUSERID_NONE; // 获取平台用户ID
	// 如果我们有关联的输入动作，那么我们可以使用它
	if (AssociatedAction) // 如果关联的输入动作有效
	{
		if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem()) // 如果获取到增强输入子系统
		{
			// 我们不想对此动作应用任何修饰符或触发器，但它们是函数签名所必需的
			TArray<UInputModifier*> Modifiers; // 输入修饰符数组
			TArray<UInputTrigger*> Triggers; // 输入触发器数组
			System->InjectInputVectorForAction(AssociatedAction, Value, Modifiers, Triggers); // 为动作注入输入向量
		}
	}
	// 如果没有关联的输入动作，我们可以尝试在回退键上模拟输入
	else if (UEnhancedPlayerInput* Input = GetPlayerInput()) // 如果获取到玩家输入
	{
		const FInputDeviceId DeviceToSimulate = IPlatformInputDeviceMapper::Get().GetPrimaryInputDeviceForUser(UserId); // 获取要模拟的主要输入设备
		if(KeyToSimulate.IsValid()) // 如果要模拟的键有效
		{
			const float DeltaTime = GetWorld()->GetDeltaSeconds(); // 获取世界增量时间
			auto SimulateKeyPress = [Input, DeltaTime, DeviceToSimulate](const FKey& KeyToSim, const float Value, const EInputEvent Event) // 定义模拟按键的lambda函数
			{
				FInputKeyEventArgs Args = FInputKeyEventArgs::CreateSimulated( // 创建模拟的输入键事件参数
					KeyToSim, // 要模拟的键
					Event, // 输入事件类型
					Value, // 值
					KeyToSim.IsAnalog() ? 1 : 0, // 如果是模拟键则为1，否则为0
					DeviceToSimulate); // 要模拟的设备

				Args.DeltaTime = DeltaTime; // 设置增量时间
			
				Input->InputKey(Args); // 输入键
			};
			
			// 对于是键对"根"的键（例如Mouse2D
			// 由MouseX和MouseY键组成），我们应该为键对中的每个键调用InputKey，
			// 而不是配对键本身。这样事件可以正确累积在
			// UPlayerInput的键状态映射中。所有来自消息处理程序和视口客户端的输入事件
			// 都以这种方式工作，所以当我们模拟键输入时，我们应该
			// 也这样做。
			if (const EKeys::FPairedKeyDetails* PairDetails = EKeys::GetPairedKeyDetails(KeyToSimulate)) // 如果要模拟的键是配对键
			{
				SimulateKeyPress(PairDetails->XKeyDetails->GetKey(), Value.X, IE_Axis); // 模拟X键输入
				SimulateKeyPress(PairDetails->YKeyDetails->GetKey(), Value.Y, IE_Axis); // 模拟Y键输入
			}
			else // 如果不是配对键
			{
				SimulateKeyPress(KeyToSimulate, Value.X, IE_Pressed); // 模拟单个键输入
			}
		}
	}
	else // 如果没有玩家输入
	{
		UE_LOG(LogLyra, Error, TEXT("'%s' is attempting to simulate input but has no player input!"), *GetNameSafe(this)); // 记录错误日志
	}
}

/**
 * 输入2D键值
 * @param Value 2D输入值
 */
void ULyraSimulatedInputWidget::InputKeyValue2D(const FVector2D& Value)
{
	InputKeyValue(FVector(Value.X, Value.Y, 0.0)); // 调用输入键值方法
}

/**
 * 刷新模拟输入
 * 刷新玩家输入中按下的键
 */
void ULyraSimulatedInputWidget::FlushSimulatedInput()
{
	if (UEnhancedPlayerInput* Input = GetPlayerInput()) // 如果获取到玩家输入
	{
		Input->FlushPressedKeys(); // 刷新按下的键
	}
}

/**
 * 查询要模拟的键
 * 从增强输入子系统查询映射到关联动作的键
 */
void ULyraSimulatedInputWidget::QueryKeyToSimulate()
{
	if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem()) // 如果获取到增强输入子系统
	{
		TArray<FKey> Keys = System->QueryKeysMappedToAction(AssociatedAction); // 查询映射到动作的键
		if(!Keys.IsEmpty() && Keys[0].IsValid()) // 如果键数组不为空且第一个键有效
		{
			KeyToSimulate = Keys[0]; // 设置要模拟的键
		}
		else // 如果没有映射的键
		{
			KeyToSimulate = FallbackBindingKey; // 设置回退绑定键
		}
	}
}

/**
 * 控制映射重建时的回调
 * 重新查询要模拟的键
 */
void ULyraSimulatedInputWidget::OnControlMappingsRebuilt()
{
	QueryKeyToSimulate(); // 查询要模拟的键
}

#undef LOCTEXT_NAMESPACE // 取消定义本地化文本命名空间